//
//  GithubSignUpViewController.swift
//  rxExample
//
//  Created by 刘山 on 2021/11/4.
//

import Foundation
import UIKit
import RxSwift
import RxCocoa
import SnapKit
extension String {
    var urlEscaped: String {
        self.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
    }
}

enum ValidationResult {
    case ok(message:String)
    case empty
    case validating
    case failed(message:String)
}

struct ValidationColors {
    static let okColor = UIColor(red: 138.0 / 255.0, green: 221.0 / 255.0, blue: 109.0 / 255.0, alpha: 1.0)
    static let errorColor = UIColor.red
}

extension ValidationResult {
    var isValid: Bool {
        switch self {
        case .ok:
            return true
        default:
            return false
        }
    }
    
    var textColor: UIColor {
        switch self {
        case .ok:
            return ValidationColors.okColor
        case .empty:
            return UIColor.black
        case .validating:
            return UIColor.black
        default:
            return ValidationColors.errorColor
        }
    }
    
    var description: String {
        switch self {
        case let .ok(message):
            return message
        case .validating:
            return "validating..."
        case let .failed(message):
            return message
        default:
            return ""
        }
    }
}

extension Reactive where Base: UILabel {
    var validationResult: Binder<ValidationResult> {
        return Binder(self.base) { (label, result) in
            label.textColor = result.textColor
            label.text = result.description
        }
    }
}

// MARK: - Service层

// MARK: 提供输入验证服务
protocol GitHubValidationService {
    func validateUsername(_ username: String) -> Observable<ValidationResult>
    func validatePassword(_ password: String) -> ValidationResult
    func validateRepeatedPassword(_ password: String, repeatedPassword: String) -> ValidationResult
}
// MARK: GitHub 网络服务
protocol GitHubAPI {
    func usernameAvailable (_ username: String) -> Observable<Bool>
    func signup(_ username: String, password: String) -> Observable<Bool>
}

// MARK: 服务实现层
class GitHubDefaultAPI: GitHubAPI {
    let URLSession:Foundation.URLSession
    init(URLSession:Foundation.URLSession) {
        self.URLSession = URLSession
    }
    static let sharedAPI = GitHubDefaultAPI(URLSession: Foundation.URLSession.shared)

    func usernameAvailable(_ username: String) -> Observable<Bool> {
        print(username)
        return self.URLSession.rx.response(request: URLRequest(url: URL(string: "https://github.com/\(username.urlEscaped)")!))
            .map { (response, _) in
                response.statusCode == 404
            }
            .catchErrorJustReturn(false)
    }
    func signup(_ username: String, password: String) -> Observable<Bool> {
        let signupResult = arc4random() % 5 == 0 ? false : true // Mock
        return Observable.just(signupResult).delay(.seconds(1), scheduler: MainScheduler.instance)
    }
}

class GitHubDefaultValidationService: GitHubValidationService {
    let API: GitHubAPI
    static let sharedValidationService = GitHubDefaultValidationService(API: GitHubDefaultAPI.sharedAPI)

    init(API:GitHubAPI) {
        self.API = API
    }

    func validateUsername(_ username: String) -> Observable<ValidationResult> {
        return API.usernameAvailable(username).map { available in
            if available {
                return ValidationResult.ok(message: "Username available")
            } else {
                return .failed(message: "Username already taken")
            }
        }.startWith(ValidationResult.validating)
    }
    
    let minPasswordCount = 5
    func validatePassword(_ password: String) -> ValidationResult {
        let numberOfCharacters = password.count
        if numberOfCharacters == 0 {
            return .empty
        }
        if numberOfCharacters < minPasswordCount {
            return .failed(message: "Password must be at least \(minPasswordCount) characters")
        }
        return .ok(message: "Password acceptable")
    }
    
    func validateRepeatedPassword(_ password: String, repeatedPassword: String) -> ValidationResult {
        if repeatedPassword.count == 0 {
            return .empty
        }
        if repeatedPassword == password {
            return .ok(message: "Password repeated")
        }
        return .failed(message: "Password different")
    }
}

// MARK: - VM层
class GithubSignUpViewModel {
    let validatedUsername: Observable<ValidationResult>
    let validatedPassword: Observable<ValidationResult>
    let validatedPasswordRepeated: Observable<ValidationResult>
    
    let signedIn: Observable<Bool>
    let signupEnabled: Observable<Bool>

    init(input:(
        username: Observable<String> ,
        password: Observable<String> ,
        repeatedPassword: Observable<String>,
        loginTaps: Observable<Void>
    ), dependency:(
        API: GitHubAPI,
        validationService: GitHubDefaultValidationService
    )) {
        // 用户名校验
        validatedUsername = input.username.flatMapLatest { username in
            return dependency.validationService
                .validateUsername(username)
                .observeOn(MainScheduler.instance)
                .catchErrorJustReturn(ValidationResult.failed(message: "Error contacting server"))
        }.share(replay: 1, scope: .forever)
        // 密码校验
        validatedPassword = input.password.map { passwd in
            return dependency.validationService.validatePassword(passwd)
        }.share(replay: 1, scope: .forever)
        // 重复密码确定
        validatedPasswordRepeated = Observable
            .combineLatest(input.password, input.repeatedPassword,resultSelector: dependency.validationService.validateRepeatedPassword)
            .share(replay: 1, scope: .forever)
        
        let usernameAndPassword = Observable.combineLatest(input.username, input.password) { (username: $0, password: $1)}
        signedIn = input.loginTaps
            .withLatestFrom(usernameAndPassword)
            .flatMapLatest({ (username: String, password: String) in
                return dependency.API.signup(username, password: password)
                    .observeOn(MainScheduler.instance)
                    .catchErrorJustReturn(false)
            })
            .share(replay: 1, scope: .forever)
        
        signupEnabled = Observable.combineLatest(validatedUsername, validatedPassword, validatedPasswordRepeated) { username, passsword, passwordRpt in
            username.isValid && passsword.isValid && passwordRpt.isValid
        }
        //.distinctUntilChanged()
        .share(replay: 1, scope: .forever)
        
    }
}

@objc(GithubSignUpViewController)
public class GithubSignUpViewController: UIViewController {
    let disposeBag = DisposeBag()

    private var usernameOutlet = UITextField()
    private var usernameValidationOutlet = UILabel()

    private var passwordOutlet = UITextField()
    private var passwordValidationOutlet = UILabel()

    private var repeatedPasswordOutlet = UITextField()
    private var repeatedPasswordValidationOutlet = UILabel()

    private var signupOutlet = UIButton()
    private var signingUpOulet = UIActivityIndicatorView()

    private var viewModel:GithubSignUpViewModel!

    public override func viewDidLoad() {
        self.view.backgroundColor = UIColor.lightGray
        layout()

        viewModel = GithubSignUpViewModel(
            input: (
                username: usernameOutlet.rx.text.orEmpty.asObservable(),
                password: passwordOutlet.rx.text.orEmpty.asObservable(),
                repeatedPassword: repeatedPasswordOutlet.rx.text.orEmpty.asObservable(),
                loginTaps: signupOutlet.rx.tap.asObservable()
            ),
            dependency: (
                API: GitHubDefaultAPI.sharedAPI ,
                validationService: GitHubDefaultValidationService.sharedValidationService
            )
        )

        viewModel.validatedUsername.bind(to: usernameValidationOutlet.rx.validationResult).disposed(by: disposeBag)
        viewModel.validatedPassword.bind(to: passwordValidationOutlet.rx.validationResult).disposed(by: disposeBag)
        viewModel.validatedPasswordRepeated.bind(to: repeatedPasswordValidationOutlet.rx.validationResult).disposed(by: disposeBag)
        viewModel.signedIn.subscribe { [weak self] valid in
            self?.signupOutlet.isEnabled = valid.element!
            self?.signupOutlet.alpha = valid.element! ? 1.0 : 0.5
        }.disposed(by: disposeBag)
    }

    func layout() {
        self.view.addSubview(usernameOutlet)
        self.view.addSubview(usernameValidationOutlet)
        self.view.addSubview(passwordOutlet)
        self.view.addSubview(passwordValidationOutlet)
        self.view.addSubview(repeatedPasswordOutlet)
        self.view.addSubview(repeatedPasswordValidationOutlet)
        self.view.addSubview(signupOutlet)
        self.view.addSubview(signingUpOulet)

        usernameOutlet.placeholder = "用户名："
        passwordOutlet.placeholder = "密码："
        repeatedPasswordOutlet.placeholder = "重复密码："

        usernameOutlet.borderStyle = .roundedRect
        usernameOutlet.clearButtonMode = .whileEditing
        usernameOutlet.keyboardType = .emailAddress
        usernameOutlet.returnKeyType = .done

        passwordOutlet.borderStyle = .roundedRect
        passwordOutlet.clearButtonMode = .whileEditing
        passwordOutlet.keyboardType = .emailAddress
        passwordOutlet.returnKeyType = .done

        repeatedPasswordOutlet.borderStyle = .roundedRect
        repeatedPasswordOutlet.clearButtonMode = .whileEditing
        repeatedPasswordOutlet.keyboardType = .emailAddress
        repeatedPasswordOutlet.returnKeyType = .done

        signupOutlet.setTitle("Sign Up", for: .normal)
        signupOutlet.backgroundColor = UIColor.blue
        
        usernameOutlet.snp.makeConstraints { make in
            make.left.equalTo(self.view).offset(30)
            make.right.equalTo(self.view).offset(-30)
            make.top.equalTo(self.view).offset(100)
        }
        usernameValidationOutlet.snp.makeConstraints { make in
            make.left.equalTo(self.view).offset(30)
            make.right.equalTo(self.view).offset(-30)
            make.top.equalTo(usernameOutlet.snp_bottom).offset(10)
        }

        passwordOutlet.snp.makeConstraints { make in
            make.left.equalTo(self.view).offset(30)
            make.right.equalTo(self.view).offset(-30)
            make.top.equalTo(usernameValidationOutlet.snp_bottom).offset(10)
        }
        passwordValidationOutlet.snp.makeConstraints { make in
            make.left.equalTo(self.view).offset(30)
            make.right.equalTo(self.view).offset(-30)
            make.top.equalTo(passwordOutlet.snp_bottom).offset(10)
        }

        repeatedPasswordOutlet.snp.makeConstraints { make in
            make.left.equalTo(self.view).offset(30)
            make.right.equalTo(self.view).offset(-30)
            make.top.equalTo(passwordValidationOutlet.snp_bottom).offset(10)
        }
        repeatedPasswordValidationOutlet.snp.makeConstraints { make in
            make.left.equalTo(self.view).offset(30)
            make.right.equalTo(self.view).offset(-30)
            make.top.equalTo(repeatedPasswordOutlet.snp_bottom).offset(10)
        }

        signupOutlet.snp.makeConstraints { make in
            make.left.equalTo(self.view).offset(30)
            make.right.equalTo(self.view).offset(-30)
            make.top.equalTo(repeatedPasswordValidationOutlet.snp_bottom).offset(30)
        }
    }
}
